Skip to content

fix: configure_logging targets 'mcp' logger instead of root logger (fixes #2527)#2534

Open
weiguangli-io wants to merge 1 commit intomodelcontextprotocol:mainfrom
weiguangli-io:fix/fastmcp-logger-pollution-2527
Open

fix: configure_logging targets 'mcp' logger instead of root logger (fixes #2527)#2534
weiguangli-io wants to merge 1 commit intomodelcontextprotocol:mainfrom
weiguangli-io:fix/fastmcp-logger-pollution-2527

Conversation

@weiguangli-io
Copy link
Copy Markdown

Summary

Fixes #2527MCPServer.__init__ called configure_logging() which used logging.basicConfig() to install a RichHandler on the root logger at INFO level. This caused every third-party library (httpx, urllib3, openai, qdrant-client, …) to emit INFO records through Rich → stderr. In stdio MCP servers spawned by Node.js hosts (Claude Desktop, Cline, qwen-code), the kernel's stderr SNDBUF (8 KB on macOS) fills under modest log volume and write(2) blocks the asyncio event loop, deadlocking the server.

Changes

  • configure_logging() now configures logging.getLogger("mcp") instead of the root logger via basicConfig.
  • Sets mcp_logger.propagate = False so records don't bubble up to any root handlers.
  • Made idempotent: skips setup if the mcp logger already has handlers, allowing applications to configure logging before constructing MCPServer.
  • Default log level changed "INFO""WARNING" — reduces noise from transitive dependencies in production/stdio deployments. Users can pass log_level="INFO" (or "DEBUG") explicitly when they want verbose output.
  • MCPServer.__init__ log_level parameter default also changed to "WARNING".
  • Added regression tests in tests/issues/test_2527_fastmcp_logger_pollution.py.

Before / After

Before (broken):

def configure_logging(level="INFO"):
    handlers = [RichHandler(console=Console(stderr=True), ...)]
    logging.basicConfig(level=level, handlers=handlers)  # ← root logger!

After (fixed):

def configure_logging(level="WARNING"):
    mcp_logger = logging.getLogger("mcp")
    if mcp_logger.handlers:
        return  # idempotent
    mcp_logger.addHandler(RichHandler(console=Console(stderr=True), ...))
    mcp_logger.setLevel(level)
    mcp_logger.propagate = False  # don't reach root

Test plan

  • uv run pytest tests/issues/test_2527_fastmcp_logger_pollution.py -v — 5 new regression tests, all pass
  • uv run pytest tests/server/mcpserver/test_server.py -v — 89 existing tests, all pass

🤖 Generated with Claude Code

…modelcontextprotocol#2527)

Previously configure_logging() called logging.basicConfig(), which adds a
RichHandler at INFO level to the **root** logger. This caused every library
(httpx, urllib3, openai, …) to emit INFO records through Rich → stderr. In
stdio MCP servers spawned by Node.js hosts (Claude Desktop, Cline, …) the
kernel's stderr SNDBUF (8 KB on macOS) fills under modest log volume, and
write(2) blocks the asyncio event loop, deadlocking the server.

Changes:
- configure_logging() now targets logging.getLogger("mcp") instead of the
  root logger via basicConfig.
- Sets mcp_logger.propagate = False so records don't reach any root handlers.
- Made idempotent: skips setup if the mcp logger already has handlers,
  allowing application code to configure logging before MCPServer.__init__.
- Default log level changed from "INFO" to "WARNING" to reduce noise from
  MCP's own internals in production/stdio deployments.
- MCPServer.__init__ log_level parameter default also changed to "WARNING".
- Added regression tests in tests/issues/test_2527_fastmcp_logger_pollution.py.

Fixes modelcontextprotocol#2527

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FastMCP.__init__ clobbers root logger with INFO RichHandler — hangs stdio servers under back-pressure

1 participant